#include <cassert>
#include <climits>
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <limits.h>
#include <list>
#include <log4cplus/logger.h>
#include <log4cplus/consoleappender.h>
#include <log4cplus/fileappender.h>
#include <map>
#include <stdlib.h>
#include <sys/stat.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

#include "xdtls_srtp.h"
#include "mod/vp8/vp8depacketizer.h"
#include "rav_record.h"
#include "rtp_writer.hpp"
#include "rtp_reader.hpp"
#include "rtp_pre_proc.hpp"

static const char* shortOptions = "hvg:AVf:t:i:o:";

enum Options {
    OptionHelp          = 'h',
    OptionVerbose       = 'v',
    OptionLogFile       = 'g',
    OptionNoAudio       = 'A',
    OptionNoVideo       = 'V',
    OptionFrom          = 'f',
    OptionTo            = 't',
    OptionInput         = 'i',
    OptionOutput        = 'o',
};

static const struct option longOptions[] = {
        {   "help",         no_argument,        nullptr,    OptionHelp      },
        {   "verbose",      no_argument,        nullptr,    OptionVerbose   },
        {   "log",          required_argument,  nullptr,    OptionLogFile   },
        {   "no-audio",     no_argument,        nullptr,    OptionNoAudio   },
        {   "no-video",     no_argument,        nullptr,    OptionNoVideo   },
        {   "from",         required_argument,  nullptr,    OptionFrom      },
        {   "to",           required_argument,  nullptr,    OptionTo        },
        {   "input",        required_argument,  nullptr,    OptionInput     },
        {   "output",       required_argument,  nullptr,    OptionOutput    },
        {   0,              0,                  0,          0               },
};

void printUsage(const char* progName) {
    fprintf(stderr, "Usage: %s [options]\n", progName);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -h,--help                 print this                 \n");
    fprintf(stderr, "  -v,--verbose              output more                \n");
    fprintf(stderr, "  -g,--log <log-file>       write log to this file     \n");
    fprintf(stderr, "  -A,--no-audio             ignore audio               \n");
    fprintf(stderr, "  -V,--no-video             ignore video               \n");
    fprintf(stderr, "  -f,--from <from>          begin of packet timestamp  \n");
    fprintf(stderr, "  -t,--to <to>              end of packet timestamp    \n");
    fprintf(stderr, "  -i,--input <tlv>          input tlv file             \n");
    fprintf(stderr, "  -o,--output <media-file>  output media file without ext\n");

}

int main(int argc, char *argv[]) {
    // medooze logging
    Logger::EnableDebug(false);
    Logger::EnableUltraDebug(false);

    int verbosity = 0;
    std::string logFile;
    std::list<TimeRange> time_ranges;
    bool read_audio = true;
    bool read_video = true;
    std::string input_file;
    std::string output_file;

    int optionIndex = 0;
    int c;
    while((c = getopt_long(argc, argv, shortOptions, longOptions, &optionIndex)) != -1) {
        switch(c) {
            case OptionHelp:
                printUsage(argv[0]);
                return 0;
            case OptionVerbose:
                ++verbosity;
                break;
            case OptionLogFile:
                logFile = std::string(optarg);
                break;
            case OptionNoAudio: {
                read_audio = false;
            } break;
            case OptionNoVideo: {
                read_video = false;
            } break;
            case OptionFrom: {
                uint64_t from = strtoull(optarg, nullptr, 10);
                time_ranges.emplace_back(TimeRange{from, (uint64_t)-1});
            } break;
            case OptionTo: {
                uint64_t to = strtoull(optarg, nullptr, 10);
                if (time_ranges.empty()) {
                    time_ranges.push_back(TimeRange{0, to});
                } else {
                    time_ranges.back().to = to;
                }
            } break;
            case OptionInput: {
                size_t l = strlen(optarg);
                if (l < 5 || strcasecmp(optarg + l - 3, "tlv") != 0) {
                    std::cerr << "input must be tlv" << std::endl;
                    exit(-1);
                }
                char fn_in[PATH_MAX] = {0,};
                realpath(optarg, fn_in);
                struct stat buf;
                int err = stat(fn_in, &buf);
                if (err != 0) {
                    std::cerr << "cannot stat " << std::string(fn_in) << std::endl;
                    exit(-1);
                }

                input_file = std::string(fn_in);
            } break;
            case OptionOutput: {
                //char fn_out[PATH_MAX] = {0,};
                //realpath(optarg, fn_out);
                output_file = std::string(optarg);
            } break;
            case '?':
                if (optopt == 'g' || optopt == 'f' || optopt == 't' || optopt == 'i' || optopt == 'o' ){
                    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                } else if (isprint (optopt)){
                    fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                } else{
                    fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                }
                printUsage(argv[0]);
                return 0;
            default:
                fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                printUsage(argv[0]);
                return 0;
        }
    }

    log4cplus::initialize();
    auto rootLogger = log4cplus::Logger::getRoot();
    rootLogger.setLogLevel(log4cplus::ALL_LOG_LEVEL);

    auto consoleAppender = log4cplus::SharedAppenderPtr(new log4cplus::ConsoleAppender());
    consoleAppender->setLayout(std::auto_ptr<log4cplus::Layout>(new log4cplus::PatternLayout("%-5p - %m %n")));
    if (verbosity >= 1) {
        consoleAppender->setThreshold(log4cplus::DEBUG_LOG_LEVEL);
    } else {
        consoleAppender->setThreshold(log4cplus::INFO_LOG_LEVEL);
    }
    rootLogger.addAppender(consoleAppender);

    av_register_all();
    avcodec_register_all();
    if (verbosity > 1) {
        av_log_set_level(AV_LOG_TRACE);
    } else if (verbosity > 0) {
        av_log_set_level(AV_LOG_INFO);
    }

    if (!logFile.empty()) {
        auto debugAppender = log4cplus::SharedAppenderPtr(new log4cplus::RollingFileAppender(logFile, 100*1024*1024, 10, false));
        debugAppender->setLayout(std::auto_ptr<log4cplus::Layout>(new log4cplus::PatternLayout("%-5p - %m %n")));
        debugAppender->setThreshold(log4cplus::ALL_LOG_LEVEL);
        rootLogger.addAppender(debugAppender);
    }

    {
        const char * cert_file = NULL; // "/Users/simon/Desktop/simon/projects/easemob/src/xmedia/dtls/certs/server-cert.pem";
        const char * key_file = NULL; // "/Users/simon/Desktop/simon/projects/easemob/src/xmedia/dtls/certs/server-key.pem";
        int ret = xdtls_srtp_init(cert_file, key_file);
        if(ret < 0){
            FMT_ERROR("failed to init dtls", FMT());
            // TODO: log error
            exit(-1);
        }
    }

    if (time_ranges.size() > 1) {
        time_ranges.sort([](const TimeRange &a, const TimeRange &b) {
            return a.from < b.from;
        });

        std::list<TimeRange> collapsed;
        collapsed.emplace_back(time_ranges.front());
        std::list<TimeRange>::iterator curr = time_ranges.begin()++;
        while (curr != time_ranges.end()) {
            TimeRange last = collapsed.back();
            if (last.to > curr->from) {
                collapsed.pop_back();
                collapsed.emplace_back(TimeRange{last.from, curr->to});
            } else {
                collapsed.emplace_back(*curr);
            }
            curr++;
        }
        time_ranges = collapsed;

        for (auto& tr : time_ranges) {
            FMT_INFO("time range: ", FMT().kv("from", tr.from).kv("to", tr.to));
        }
    }



    FMT_INFO("pre-process begin", FMT());
    RTPReader reader;
    RTPPreProcess pre;

    reader.input(input_file);
    reader.range(time_ranges);
    reader.audio(read_audio);
    reader.video(read_video);
    reader.handler(&pre);
    reader.run();
    FMT_INFO("pre-process complete", FMT());
    FMT_INFO("trans-format begin", FMT());

    RTPWriter writer;
    if (pre.hasAudio()) {
        writer.audio(pre.getAudioCodec(), pre.getAudioSampleRate(), pre.getAudioChannels());
    }
    if (pre.hasVideo()) {
        writer.video(pre.getVideoCodec(), pre.getVideoWidth(), pre.getVideoHeight(), 15);
    }
    writer.output(output_file);
    reader.time().dump();
    // todo: reset reader properly, then we do not need another one
    RTPReader reader2;
    reader2.input(input_file);
    reader2.range(time_ranges);
    reader2.audio(read_audio);
    reader2.video(read_video);
    reader2.handler(&writer);
    reader2.time(reader.time());
    reader2.run();

    // todo: remove this close call
    writer.close();

    FMT_INFO("output complete", FMT());
    return 0;
}
